using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using SlimDX;
using Barrage.Global;
using Barrage.Language;

namespace Barrage.GUI.Elements
{
    /// <summary>
    /// Event that fires when a GUISelector item changes its value
    /// </summary>
    /// <param name="buttonId">GUIElement id</param>
    /// <param name="selection">Selection index</param>
    /// <param name="value">Selected value</param>
    public delegate void GUISelectorHandler(string buttonId,int selection,string value);
    /// <summary>
    /// GUISelector allows to select an index/value combination from an item list
    /// </summary>
    public class GUISelector : GUISelectableElement
    {
        const string XML_WIDTH_ATTRIBUTE = "width";
        const string XML_HEIGHT_ATTRIBUTE = "height";
        const string XML_TEXT_ATTRIBUTE = "text";
        const string XML_OPTION_ID_ATTRIBUTE = "id";
        const string XML_OPTION_ELEMENT = "Option";
        const string XML_SELECTED_ATTRIBUTE = "selected";

        SortedList<int, string> options = new SortedList<int, string>(1);
        /// <summary>
        /// List of Options ordered by an id
        /// </summary>
        public SortedList<int, string> Options { get { return options; } }
        int currentOption = -1;

        /// <summary>
        /// Fires when CurrentOption value changes
        /// </summary>
        public event GUISelectorHandler OnInteraction;

        /// <summary>
        /// Size for mouse cursor calculations
        /// </summary>
        protected Vector2 size;
        /// <summary>
        /// Size for mouse cursor calculations
        /// </summary>
        public Vector2 Size { get { return size; } }

        /// <summary>
        /// Returns the currently selected option value, empty string if there's nothing selected
        /// </summary>
        public string CurrentOptionValue { get { string value; if (options.TryGetValue(currentOption, out value)) return value; return ""; } }
        /// <summary>
        /// Returns the currently selected option index, -1 if none selected
        /// </summary>
        public int CurrentOption { get { return currentOption; } set { currentOption = value; } }

        bool hovering = false;
        /// <summary>
        /// Tells if the mouse cursor is over this item
        /// </summary>
        public bool Hovering { get { return hovering; } }

        string text;
        /// <summary>
        /// The item title text
        /// </summary>
        public virtual string Text { get { return text; } set { text = value; } }

        /// <summary>
        /// Item's default size. Can be overrided
        /// </summary>
        protected virtual Vector2 DefaultSize { get { return new Vector2(100, 100); } }

        /// <summary>
        /// Item constructor
        /// </summary>
        /// <param name="layout">Layout that will hold this item</param>
        /// <param name="template">Template that will be used to render this item</param>
        /// <param name="xn">Xml Node that holds attribute values of this item</param>
        public GUISelector(GUILayout layout, string template, XmlNode xn)
            : base(layout, template, xn)
        {
            size = GameMath.ParseVector2(XmlHelper.GetXMLAttribute(xn, XML_WIDTH_ATTRIBUTE), XmlHelper.GetXMLAttribute(xn, XML_HEIGHT_ATTRIBUTE), DefaultSize);
            text = Language.LanguageManager.Texts[XmlHelper.GetXMLAttribute(xn, XML_TEXT_ATTRIBUTE)];

            foreach(XmlNode n in xn.ChildNodes)
            {
                if (n.Name == XML_OPTION_ELEMENT)
                {
                    int id = XmlHelper.ParseXMLIntAttribute(n, XML_OPTION_ID_ATTRIBUTE);
                    if (XmlHelper.ParseXMLBoolAttribute(n, XML_SELECTED_ATTRIBUTE))
                        currentOption = id;
                    options.Add(id, LanguageManager.Texts[XmlHelper.GetXMLAttribute(n, XML_TEXT_ATTRIBUTE)]);
                }
            }
            if ((currentOption == -1)&&options.Count>0) currentOption = options.Keys[0];
        }

        /// <summary>
        /// Adds an option to the option list
        /// </summary>
        /// <param name="id">Unique option identifier</param>
        /// <param name="value">Option value</param>
        public void addOption(int id, string value)
        {
            options.Add(id, value);
            if (currentOption == -1) currentOption = id;
        }

        /// <summary>
        /// \internal Item logic
        /// </summary>
        protected override void DoUpdate()
        {
            base.DoUpdate();
            hovering = CheckIntersection();
            if (hovering) Layout.SelectedItemIndex = Order;
            CheckClick();
            if (Selected)
            {
                if (Keyboard.Instance.KeyPressed(SlimDX.DirectInput.Key.RightArrow) || Gamepad.AnyButtonStatus(SlimDX.XInput.GamepadButtonFlags.DPadRight) || Gamepad.AnyStickPressed(Stick.LeftStick, StickDirection.Right))
                    SelectNext();
                if (Keyboard.Instance.KeyPressed(SlimDX.DirectInput.Key.LeftArrow) || Gamepad.AnyButtonStatus(SlimDX.XInput.GamepadButtonFlags.DPadLeft) || Gamepad.AnyStickPressed(Stick.LeftStick, StickDirection.Left))
                    SelectPrevious();
            }
        }

        /// <summary>
        /// Selects the next option, returning to the first item in the list if necessary
        /// </summary>
        protected void SelectNext()
        {
            if (options.Count > 0)
                currentOption = options.Keys[(options.IndexOfKey(currentOption) + 1) % options.Count];
            SendEvent();
        }

        /// <summary>
        /// Selects the previous option, returning to the last item in the list if necessary
        /// </summary>
        protected void SelectPrevious()
        {
            if (options.Count > 0)
                currentOption = options.Keys[(options.Count + options.IndexOfKey(currentOption) - 1) % options.Count];
            SendEvent();
        }

        /// <summary>
        /// Sends the interaction event
        /// </summary>
        protected void SendEvent()
        {
            if (this.OnInteraction != null)
                this.OnInteraction.Invoke(Id, currentOption, CurrentOptionValue);
        }

        /// <summary>
        /// If the mouse click is over the item and a button is pressed, the current option changes
        /// </summary>
        protected void CheckClick()
        {
            if (Mouse.Instance.ButtonReleased() && hovering)
            {
                SelectNext();
            }
            if (Mouse.Instance.ButtonReleased(1) && hovering)
            {
                SelectPrevious();
            }
        }

        /// <summary>
        /// Checks mouse intersection with the item
        /// </summary>
        /// <returns>True if there's intersection</returns>
        protected bool CheckIntersection()
        {
            Vector3 mPos = new Vector3(Mouse.Instance.Position, 0);
            Vector4 tPos = GameMath.VectorAbs(Vector3.Transform(mPos, Matrix.Invert(motion.WorldMatrix)));

            return ((tPos.X <= size.X / 2f) && (tPos.Y <= size.Y / 2f));
        }

        /// <summary>
        /// \internal Default item action selects next option in the list
        /// </summary>
        public override void Interact()
        {
            SelectNext();
        }

        /// <summary>
        /// \internal Clear resources
        /// </summary>
        public override void Dispose()
        {
            options.Clear();
            options = null;
            base.Dispose();
        }
    }

}
